﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using iMapa.Utils;
using System.Timers;
using iMapa.Backend.Calibration;
using Emgu.CV;
using Emgu.CV.Structure;
using System.Drawing;
using iMapa.UI.ModelView.Main;



namespace iMapa.Backend
{
    class SimulationController
    {
        private static SimulationController singleton = new SimulationController();

        private MessageQueue calibrationRequestQueue;
        private MessageQueue calibrationResponseQueue;
        private AutoCalibration calibration;
        
        private SpotDetector detector;
        private ActionExecutor executor;
        private bool detectionInProgress;
        //public event EventHandler<SpotDetectionArgs> SpotDetected;
        private Image<Bgr, byte> oldImage;
        private Point? lastDetectedPoint;

        private Timer calibrationRequestProducerTimer;
        private Timer calibrationResponseConsumerTimer;

        private List<long> calibrationDurations=new List<long>();


        private GroupViewModel gmv;
        

        private SimulationController()
        {
            calibrationRequestQueue = new MessageQueue(1);
            calibrationResponseQueue = new MessageQueue();
            calibration = new AutoCalibration(calibrationRequestQueue, calibrationResponseQueue);
            executor = new ActionExecutor();
            detector = new SpotDetector();
        }

        private void ResetTimers()
        {
            if (calibrationRequestProducerTimer != null)
            {
                calibrationRequestProducerTimer.Close();
                calibrationRequestProducerTimer.Dispose();
            }
            calibrationDurations.Clear();
            calibrationRequestProducerTimer = new Timer(1000);
            calibrationRequestProducerTimer.Elapsed += CalibrationRequestTimerTick;
            calibrationRequestProducerTimer.AutoReset = true;
            
            if (calibrationResponseConsumerTimer != null)
            {
                calibrationResponseConsumerTimer.Close();
                calibrationResponseConsumerTimer.Dispose();
            }
            calibrationResponseConsumerTimer = new Timer(50);
            calibrationResponseConsumerTimer.Elapsed += CalibrationResponseTimerTick;
            calibrationResponseConsumerTimer.Elapsed += ProcessSpotDetection;
            calibrationResponseConsumerTimer.AutoReset = true;
        }

        public void StartSimulation(GroupViewModel img)
        {
            if (calibration.Stopped)
            {
                this.gmv = img;
                calibrationRequestQueue.ResetQueue();
                calibrationResponseQueue.ResetQueue();
                calibration.Start(img.Name);
                detector.Img = img.Igroup;
                executor.InitExecution(img.Igroup);
                ResetTimers();
                calibrationResponseConsumerTimer.Enabled = true;
                calibrationRequestProducerTimer.Enabled = true;
                gmv.onSimulationChanged();
            }
            else
            {
                System.Diagnostics.Trace.TraceError("Simulation already running...stop before starting new.");
            }
        }

        private void CalibrationRequestTimerTick(object sender, EventArgs e)
        {
            Calibrate();
        }

        private void CalibrationResponseTimerTick(object sender, EventArgs e)
        {
            ConsumeCalibrationResponse();
        }

        private void ProcessSpotDetection(object sender, EventArgs e)
        {
            if (detectionInProgress || calibration.Stopping || calibration.Stopped) {
                return;
            }
            detectionInProgress = true;
            Image<Bgr, byte> frame = CameraInput.getInstance().getImage();
            if (!ImgUtils.IsImagesSame(frame, oldImage))
            {
                Point? p = detector.getSpot(frame);
                lastDetectedPoint = p ;
                if (p.HasValue)
                {
                    //SpotDetected.Invoke(this, new SpotDetectionArgs(p.Value));
                    
                    executor.ProcessFoundSpotPoint(p.Value);
                }
            }
            if (oldImage != null) { oldImage.Dispose(); }
            oldImage = frame;
            detectionInProgress = false;
        }

        private void Calibrate()
        {
            Image<Bgr, byte> frame = CameraInput.getInstance().getImage();
            List<MapCalibrateRequest> mr = new List<MapCalibrateRequest>();
            for(int i=0 ; i<gmv.Maps.Count;i++)
            {
                InteractMap map= gmv.Igroup.Maps[i];
                MapCalibrateRequest r = new MapCalibrateRequest();
                r.MapID = i;
                r.ModelFeatures = map.ModelFeatures;
                r.ModelImageWidth = map.ModelImage.Size.Width;
                r.ModelImageHeight = map.ModelImage.Size.Height;
                mr.Add(r);
                
            }
            CalibrationRequest request = new CalibrationRequest();
            request.Frame = frame;
            request.MapRequests = mr;
            calibrationRequestQueue.Produce(request);
        }

        private void ConsumeCalibrationResponse()
        {
            object o;
            
            while ((o = calibrationResponseQueue.ConsumeNoWait()) != null)
            {
                if (o is CalibrationResponse)
                {
                    CalibrationResponse response = (CalibrationResponse)o;
                    ModifyCalibrationInterval(response.Duration);
                    for(int i = 0; i< response.ModelOnObservedImage.Length;i++ )
                    {
                        gmv.Igroup.Maps[i].Enqueue(response.ModelOnObservedImage[i]);

                    }
                }
                else
                {
                    //cosumed last simulation object- simulation stopped
                    calibrationResponseConsumerTimer.Enabled = false;
                    RunningAverage.getInstance().clear();
                    executor.ResetExecution();
                }
            }
            gmv.onSimulationChanged();
        }

        private void ModifyCalibrationInterval(long p)
        {
            if (calibrationDurations.Count < 10)
            {
                calibrationDurations.Add(p);
                calibrationRequestProducerTimer.Interval = p * 1.5;
            }
            else if (calibrationDurations.Count == 10)
            {
                calibrationRequestProducerTimer.Interval = calibrationDurations.Average()*1.5;
            }

        }

        public void ManualCalibrationInvoke()
        {
            Calibrate();
        }

        public void StopSimulation()
        {
            if (calibration != null && !calibration.Stopped && !calibration.Stopping)
            {
                calibrationRequestProducerTimer.Enabled = false;
                calibration.Stop();
                gmv.ResetAllCalibrations();
                calibrationRequestQueue.Produce(new object());
                gmv.onSimulationChanged();
            }
        }

        public static SimulationController getInstance()
        {
            return singleton;
        }

        public Boolean SimulationRunning
        {
            get { return !calibration.Stopped && !calibration.Stopping; }
        }

        public Boolean SimulationStopped
        {
            get { return calibration.Stopped; }
        }

        internal Image<Bgr, byte> DrawCalibrationToImage(Image<Bgr, byte> tmp)
        {
            List<Point[]> maplines= new List<Point[]>();
            for (int i = 0; i < gmv.Maps.Count; i++)
            {
                InteractMap map = gmv.Igroup.Maps[i];
                if (map.HMatrixToObserved != null)
                {
                    PointF[] mapPolyF = map.CountProjectionPoints(ImgUtils.getCornerPoints(map.ModelImage.Size),false);
                    if (mapPolyF != null)
                    {
                        Point[] mapPoly = ImgUtils.pointfToPoint(mapPolyF);
                        maplines.Add(mapPoly);
                    }
                }
            }
            tmp.DrawPolyline(maplines.ToArray(), true, new Bgr(Color.Azure), 4);
            if (lastDetectedPoint.HasValue)
            {
                CircleF cir = new CircleF(ImgUtils.ToPointF(lastDetectedPoint.Value), 5);
                tmp.Draw(cir, new Bgr(Color.Red), 3);
            }
            return tmp;
        }
    }

}
